/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define OMEN_SOURCE 1

#include "OmenScene.h"
#include "OmenModel.h"
#include "OmenOctree.h"
#include "OmenFactory.h"
#include <OmenCollisionEvent.h>

using namespace Omen;

#include <iostream>
#include <algorithm>

/// <summary>Default constructor</summary>
Scene::Scene()
{
    m_cameraCount = 0;
    m_lightCount  = 0;
    m_modelCount  = 0;
    
    m_pModelPtrs = NULL;
    m_pLightPtrs = NULL;
    
    m_pCurrentCamera = NULL;

    std::cout << "Omen::Scene was created!" << std::endl;
}

/// <summary>Copy constructor</summary>
Omen::Scene& Scene::operator=( const Omen::Scene& other )
{
    m_cameraCount = other.m_cameraCount;
    m_lightCount = other.m_lightCount;
    m_modelCount = other.m_modelCount;

    m_cameras = other.m_cameras;
    m_lights = other.m_lights;
    m_models = other.m_models;

    m_pCurrentCamera = other.m_pCurrentCamera;

    return *this;
}

/// <summary>Destructor</summary>
Scene::~Scene()
{
    // Delete all models allocated to this scene
    for( unsigned long i=0; i < m_models.size(); ++i )
        delete m_models[i];
    m_models.clear();

    // Delete all lights allocated to this scene
    for(unsigned long i=0; i < m_lights.size(); ++i )
        delete m_lights[i];
    m_lights.clear();

    // Delete all cameras allocated to this scene
    for(unsigned long i=0; i < m_cameras.size(); ++i )
        delete m_cameras[i];
    m_cameras.clear();

    // Delete model and light list pointers
    if( m_pModelPtrs )
        delete[] m_pModelPtrs;
    if( m_pLightPtrs )
        delete[] m_pLightPtrs;

    std::cout << "Omen::Scene was deleted!" << std::endl;
}


/// <summary>Add the contents of the given scene to this scene</summary>
void  Scene::mergeScene( const IScene* pOtherScene )
{
    for( unsigned long iCamera = 0; iCamera < pOtherScene->cameraCount(); ++iCamera )
        addCamera(pOtherScene->getCamera(iCamera));

    for( unsigned long iModel = 0; iModel < pOtherScene->modelCount(); ++iModel )
    {
        Omen::IModel* pModel = pOtherScene->getModel(iModel);
        addModel(pOtherScene->getModel(iModel));
    }

    for( unsigned long iLight = 0; iLight < pOtherScene->lightCount(); ++iLight )
        addLight(pOtherScene->getLight(iLight));
}

/// <summary>Add a new camera to the scene</summary>
void Scene::addCamera( const ICamera* pCamera )
{
    m_cameras.push_back( const_cast<ICamera*>(pCamera) );
}

/// <summary>Returns the number of cameras in the scene</summary>
unsigned long Scene::cameraCount() const
{
    return m_cameras.size();
}

/// <summary>Add a new camera to the scene</summary>
void Scene::getCameras( ICamera** pListCameras, unsigned long maxListSize ) const
{
    if( pListCameras )
    for( unsigned long i=0; i < maxListSize && i < m_cameras.size(); ++i )
        pListCameras[i] = m_cameras[i];
}

/// <summary>Returns the camera at given index </summary>
ICamera* Scene::getCamera( unsigned long iCamera ) const
{
    if( iCamera <= m_cameras.size() )
        return m_cameras[iCamera];
    else
        return NULL;
}

/// <summary>Return currently active camera</summary>
ICamera* Scene::currentCamera() const
{
    return m_pCurrentCamera;
}

/// <summary>Sets the currently active camera</summary>
void Scene::setCurrentCamera( const ICamera* pCamera ) const
{
    m_pCurrentCamera = const_cast<ICamera*>(pCamera);
}

/// models
/// <summary>Add a new Model to the scene</summary>
void Scene::addModel( const IModel* pModel )
{
    if( pModel )
    {
        if( std::find( m_models.begin(), m_models.end(), pModel ) == m_models.end() )
        {m_models.push_back( const_cast<IModel*>(pModel) );
        const_cast<Omen::IModel*>(pModel)->setScene(this);
        }
    }
}

/// <summary>Add a new Model to the scene</summary>
void  Scene::getModels( IModel** pListModels, unsigned long maxListSize ) const
{
    if( pListModels )
    for( unsigned long i=0; i < maxListSize && i < m_models.size(); ++i )
        pListModels[i] = m_models[i];
}


void Scene::checkCollisions()
{
    if( m_pCurrentCamera )
    {
        Omen::Vector3d cameraVelocity = m_pCurrentCamera->velocity();
        if( cameraVelocity.length() > .0f )
        {
            Omen::SphereBoundingVolume sphere0 = *dynamic_cast<SphereBoundingVolume*>(m_pCurrentCamera->boundingVolume(.0f));
            Omen::SphereBoundingVolume sphere1 = *dynamic_cast<SphereBoundingVolume*>(m_pCurrentCamera->boundingVolume(.001f));

            if( sphere0.position() == sphere1.position() )
            {
                return;
            }
        
            Omen::Vector3d C0 = sphere0.position();
            Omen::Vector3d C1 = sphere1.position();
            Omen::Float r = sphere1.radius();
        
            std::vector< Omen::IModel* >::iterator iter = m_models.begin();
        
            while( iter != m_models.end() )
            {
                Omen::IModel* pModel = *iter;
            
                if( pModel->name() == L"SkyBox" || pModel->name() == L"Grid" || pModel->name() == L"Sphere" )
                {
                    ++iter;
                    continue;
                }
                Omen::AABB aabb = pModel->boundingBox();
                Omen::AABBVolume box(aabb.minimum(), aabb.maximum());
                if( sphere1.collides( &box ) )
                {
                    std::wcout << L"Collision of AABB and camera sphere with object " << pModel->name() << std::endl;
                    Omen::Vector3d Ci; // Point of intersection
                    Omen::IMesh* pMesh = pModel->getMesh(0);
                    for( unsigned long i = 0; i < pMesh->faceCount(); ++i )
                    {
                        std::vector<Omen::IMesh::Face>::iterator face;
                        std::vector<Omen::IMesh::Vertex>::iterator vertex;
                        pMesh->getFace(i,face);
                        Omen::Vector3d a,b,c;
                        pMesh->getVertex(face->p1,vertex);a = Omen::Vector3d( vertex->x, vertex->y, vertex->z );
                        pMesh->getVertex(face->p2,vertex);b = Omen::Vector3d( vertex->x, vertex->y, vertex->z );
                        pMesh->getVertex(face->p3,vertex);c = Omen::Vector3d( vertex->x, vertex->y, vertex->z );
                        Omen::Vector3d n = pMesh->faceNormal(*face);

                        Omen::Plane plane(n,a);

                        Omen::Float d0 = Vector3d::dotProduct(C0,n)+plane.d;
                        Omen::Float d1 = Vector3d::dotProduct(C1,n)+plane.d;
                    
                        // check if the we were colliding already in the previous frame
                        if( d0 <= sphere1.radius() )
                        {
                            Ci = sphere1.position();
                            Omen::Float u = (d0-r)/(d0-d1);
                            Ci = (1.0f-u)*C0 + u*C1;
                            std::cout << "Collision In previous frame at: " << Ci.x << ", " <<  Ci.y << ", " << Ci.z << std::endl;
                            GetOmenFactory().getKernel().sendEvent( new Omen::CollisionEvent( m_pCurrentCamera, pModel, plane, Ci ) );
                        }
                        // If we are about to collide during this frame
                        if( d0 > r && d1 < r )
                        {
                            Omen::Float u = (d0-r)/(d0-d1);
                            Ci = (1.0f-u)*C0 + u*C1;
                            std::cout << "Collision NOW at: " << Ci.x << ", " <<  Ci.y << ", " << Ci.z << std::endl;
                            GetOmenFactory().getKernel().sendEvent( new Omen::CollisionEvent( m_pCurrentCamera, pModel, plane, Ci ) );
                        }

                    
                    }
                }
                ++iter;
            }
        }
    }
}

Omen::IModel* Scene::findModel( const std::wstring& modelName ) const
{
    for( unsigned long i=0; i < m_models.size(); ++i )
    {
        std::wstring name;
        m_models[i]->getProperty( L"name", name );
        if( name == modelName )
            return m_models[i];
    }
    return NULL;
}

/// <summary>Returns model in given index </summary>
IModel* Scene::getModel( unsigned long iModel ) const
{
    if( iModel <= m_models.size() )
        return m_models[iModel];
    else
        return NULL;
}

/// <summary>Returns the number of models in the scene</summary>
unsigned long Scene::modelCount() const
{
    return m_models.size();
}

/// lights
/// <summary>Add a new Light to the scene</summary>
void Scene::addLight( const ILight* pLight )
{
    m_lights.push_back( const_cast<ILight*>(pLight) );
}                        
    
/// <summary>Add a new Light to the scene</summary>
void Scene::getLights( ILight** pListLights, unsigned long maxListSize ) const
{
    if( pListLights )
    for( unsigned long i=0; i < maxListSize && i < m_lights.size(); ++i )
        pListLights[i] = m_lights[i];
}

/// <summary>Returns the camera at given index </summary>
ILight* Scene::getLight( unsigned long iLight ) const
{
    if( iLight <= m_lights.size() )
        return m_lights[iLight];
    else
        return NULL;
}

/// <summary>Returns the number of cameras in the scene</summary>
unsigned long Scene::lightCount() const
{
    return m_lights.size();
}

/// rendering interface
/// <summary>Render this scene</summary>
void Scene::render(const Omen::IRenderer *pRenderer) const
{
    if( pRenderer )
    {
        if( !const_cast<Scene*>(this)->reallocPtrs() )
            return;

        // Begin rendering operation
        pRenderer->beginRendering();

        // Render the cameras, i.e. set the viewing transformations
        pRenderer->render(const_cast<Omen::ICamera*>(m_pCurrentCamera));
        
        // Render models, i.e. put the vertex and index buffers on the graphics pipeline
        pRenderer->render(m_pModelPtrs, m_modelCount);

        // Render the lights, i.e. setup the light shaders
        pRenderer->render(m_pLightPtrs, m_lightCount);
        
        // End rendering operation
        pRenderer->endRendering();
    }
}

bool Scene::reallocPtrs()
{
    // If the lightcount has changed since last alloc
    if( m_lights.size() != m_lightCount )
    {
        if( m_pLightPtrs )
            delete[] m_pLightPtrs;

        m_pLightPtrs = new Omen::ILight*[m_lights.size()];

        if( !m_pLightPtrs )
            return false;

        m_lightCount = m_lights.size();

        for(std::size_t i = 0; i < m_lights.size(); ++i )
            m_pLightPtrs[i] = m_lights.at(i);
    }
    // If the modelcount has changed since last alloc
    if( m_models.size() != m_modelCount )
    {
        if( m_pModelPtrs )
            delete[] m_pModelPtrs;

        m_pModelPtrs = new Omen::IModel*[m_models.size()];

        if( !m_pModelPtrs )
            return false;

        m_modelCount = m_models.size();

        for(std::size_t i = 0; i < m_models.size(); ++i )
            m_pModelPtrs[i] = m_models.at(i);
    }
    // Both, the lights and model pointers reallocated successfully
    return true;
}

Omen::IOctree* Scene::createOctree( unsigned int maximumDepth, unsigned int minimumFaceCount )
{
    m_pOctree = ::createOctree( this, maximumDepth, minimumFaceCount );
    return m_pOctree;
}

void Scene::setOctree( Omen::IOctree* pOctree )
{
    m_pOctree = pOctree;
}

Omen::IOctree* Scene::octree() const
{
    return m_pOctree;
}


